package com.leo.auth.config;

import com.leo.auth.exception.CustomAccessDeniedHandler;
import com.leo.auth.exception.CustomAuthenticationEntryPoint;
import com.leo.auth.filter.CustomFilterInvocationSecurityMetadataSource;
import com.leo.auth.filter.CustomSecurityInterceptor;
import com.leo.auth.handler.CustomAuthenticationFailureHandler;
import com.leo.auth.handler.CustomAuthenticationSuccessHandler;
import com.leo.auth.handler.CustomLogoutSuccessHandler;
import com.leo.auth.handler.CustomSessionInformationExpiredStrategy;
import com.leo.common.security.server.handler.UserFormAuthenticationFailureHandler;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

/**
 * <p>
 * 认证相关配置
 * </p>
 *
 * @author ：Leo
 * @since ：2021-04-06 16:13
 */
@Primary
@Configuration
@Order(1)
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	private final UserDetailsService userService;
	private final CustomAuthenticationEntryPoint authenticationEntryPoint;
	private final CustomAuthenticationSuccessHandler authenticationSuccessHandler;
	private final CustomAuthenticationFailureHandler authenticationFailureHandler;
	private final CustomLogoutSuccessHandler logoutSuccessHandler;
	private final CustomSessionInformationExpiredStrategy sessionInformationExpiredStrategy;
	private final AccessDecisionManager accessDecisionManager;
	private final CustomFilterInvocationSecurityMetadataSource securityMetadataSource;
	private final CustomAccessDeniedHandler accessDeniedHandler;
	private final CustomSecurityInterceptor securityInterceptor;

	@Bean
	@Override
	@SneakyThrows
	public AuthenticationManager authenticationManagerBean() {
		return super.authenticationManagerBean();
	}

	@Bean
	public AuthenticationFailureHandler authenticationFailureHandler() {
		return new UserFormAuthenticationFailureHandler();
	}

	@Bean
	public UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider() {
		return new UsernamePasswordAuthenticationProvider();
	}

	/**
	 * https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-updated
	 * Encoded password does not look like BCrypt
	 * @return PasswordEncoder
	 */
	@Bean
	public PasswordEncoder passwordEncoder() {
		// 设置默认的加密方式（强hash方式加密）
		return new BCryptPasswordEncoder();
	}

	@Bean
	@Override
	protected UserDetailsService userDetailsService() {
		return userService;
	}

	/**
	 * http相关的配置，包括登入登出、异常处理、会话管理等
	 * @param http HttpSecurity
	 */
	@Override
	@SneakyThrows
	protected void configure(HttpSecurity http) {
		http.cors().and().csrf().disable();

		http.authorizeRequests()
				.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
					@Override
					public <O extends FilterSecurityInterceptor> O postProcess(O o) {
						o.setAccessDecisionManager(accessDecisionManager);//决策管理器
						o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
						return o;
					}
				})
				//登出
				.and().logout()
				.permitAll()
				.logoutSuccessHandler(logoutSuccessHandler)
				.deleteCookies("JSESSIONID")
				//登入
				.and().formLogin()
				.permitAll()
				.successHandler(authenticationSuccessHandler)
				.failureHandler(authenticationFailureHandler)
				//异常处理(权限拒绝、登录失效等)
				.and().exceptionHandling()
				.accessDeniedHandler(accessDeniedHandler)
				//匿名用户访问无权限资源时的异常处理
				.authenticationEntryPoint(authenticationEntryPoint)
				//会话管理
				.and().sessionManagement()
				//同一账号同时登录最大用户数
				.maximumSessions(1)
				//会话失效(账号被挤下线)处理逻辑
				.expiredSessionStrategy(sessionInformationExpiredStrategy);
		http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);
	}

	/**
	 * 配置认证方式等
	 * @param auth AuthenticationManagerBuilder
	 */
	@Override
	@SneakyThrows
	protected void configure(AuthenticationManagerBuilder auth) {
		//配置认证方式
		auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
	}

	/**
	 * 不拦截静态资源
	 * @param web WebSecurity
	 */
	@Override
	public void configure(WebSecurity web) {
		web.ignoring().antMatchers("/css/**");
	}

}
